package com.ivyft.jetty.yarn;

import com.ivyft.jetty.yarn.socket.FreeSocketPortFactory;
import com.ivyft.jetty.yarn.socket.SocketPortFactory;
import com.ivyft.jetty.yarn.util.HadoopUtils;
import com.ivyft.jetty.yarn.util.NetworkUtils;
import com.ivyft.jetty.yarn.util.ZipFileOperator;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.Options;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.yarn.api.ApplicationConstants;
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
import org.apache.hadoop.yarn.api.records.ContainerId;
import org.apache.hadoop.yarn.api.records.LocalResourceType;
import org.apache.hadoop.yarn.api.records.LocalResourceVisibility;
import org.apache.hadoop.yarn.util.ConverterUtils;
import org.eclipse.jetty.xml.XmlConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * <pre>
 *
 * Created by IntelliJ IDEA.
 * User: zhenqin
 * Date: 15/12/15
 * Time: 15:26
 * To change this template use File | Settings | File Templates.
 *
 * </pre>
 *
 * @author zhenqin
 */
public class Jetty {


    private static Logger LOG = LoggerFactory.getLogger(Jetty.class);


    public static void copyXmlFile(FileSystem fs, Path war, String localPath) throws Exception {
        String warName = war.getName();
        Path parent = war.getParent();

        if(fs.isDirectory(war)) {
            //文件夹, 直接检测是否用同名的 xml 文件
            //比如部署 hdfs:///lib/war/hello 要检测是否用 hello.xml 的 JettyContext
            Path xmlPath = new Path(parent, warName + ".xml");
            if(fs.exists(xmlPath) && fs.isFile(xmlPath)) {
                LOG.info("copy dir xml " + xmlPath + " to " + localPath);
                fs.copyToLocalFile(xmlPath, new Path(localPath));
            } else {
                LOG.warn(war + " has no " + xmlPath);
            }
        } else {
            //比如部署 hdfs:///lib/war/hello.war 要检测是否用 hello.xml 的 JettyContext
            warName = getFileNameWithoutExtention(warName);
            Path xmlPath = new Path(parent, warName + ".xml");
            if(fs.exists(xmlPath) && fs.isFile(xmlPath)) {
                LOG.info("copy war xml " + xmlPath + " to " + localPath);
                fs.copyToLocalFile(xmlPath, new Path(localPath));
            } else {
                LOG.warn(war + " has no " + xmlPath);
            }
        }
    }

    private static String getFileNameWithoutExtention(String warName) {
        int i = warName.lastIndexOf(".");
        if(i > 0) {
            return warName.substring(0, i);
        }
        return warName;
    }


    public static void main(String[] args) throws Exception {
        LOG.info("Starting the Jetty!!!!");

        Options opts = new Options();
        opts.addOption("app_attempt_id", true, "App Attempt ID. Not to be used " +
                "unless for testing purposes");

        CommandLine cl = new GnuParser().parse(opts, args);

        ApplicationAttemptId appAttemptID;
        Map<String, String> envs = System.getenv();
        if (cl.hasOption("app_attempt_id")) {
            String appIdStr = cl.getOptionValue("app_attempt_id");
            appAttemptID = ConverterUtils.toApplicationAttemptId(appIdStr);
        } else if (envs.containsKey(ApplicationConstants.Environment.CONTAINER_ID.name())) {
            ContainerId containerId = ConverterUtils.toContainerId(envs
                    .get(ApplicationConstants.Environment.CONTAINER_ID.name()));
            appAttemptID = containerId.getApplicationAttemptId();
            LOG.info("appAttemptID from env:" + appAttemptID.toString());
        } else {
            LOG.error("appAttemptID is not specified for jetty instance.");
            throw new Exception("appAttemptID is not specified for jetty instance.");
        }

        String appId = envs.get("APPLICATION_ID");

        LOG.info("container id: " + appAttemptID);

        JettyConfiguration jettyConf = new JettyConfiguration("jetty.yarn.properties");

        jettyConf.setProperty("app_attempt_id", appAttemptID);
        jettyConf.setProperty("application_id", appId);

        String jettyPort = System.getProperty("jetty.port");
        String jettyHost = System.getProperty("jetty.host", NetworkUtils.getLocalhostName());
        if(StringUtils.isNotBlank(jettyPort)) {
            int port = Integer.parseInt(jettyPort);

            SocketPortFactory factory = new FreeSocketPortFactory();
            port = factory.getSocketPort(port, jettyConf.getInt("jetty.port.step", 1));

            System.setProperty("jetty.port", String.valueOf(port));
            jettyConf.setProperty("jetty.port", String.valueOf(port));
            jettyPort = String.valueOf(port);
        } else {
            int port = jettyConf.getInt("jetty.port", 8080);

            SocketPortFactory factory = new FreeSocketPortFactory();
            port = factory.getSocketPort(port, jettyConf.getInt("jetty.port.step", 1));

            System.setProperty("jetty.port", String.valueOf(port));
            jettyConf.setProperty("jetty.port", String.valueOf(port));
            jettyPort = String.valueOf(port);
        }

        LOG.info("-Djetty.port=" + System.getProperty("jetty.port"));

        String warDirs = System.getProperty("jetty.wars");

        LOG.info("jetty.wars=" + warDirs);
        if(StringUtils.isBlank(warDirs)) {
            throw new IllegalArgumentException("jetty.war must not be blank.");
        }

        String pwd = System.getProperty("jetty.home");
        LOG.info("jetty.home=" + pwd);

        String[] wars = warDirs.split(",");
        List<Path> warPathList = new ArrayList<Path>(3);
        FileSystem fs = HadoopUtils.getFileSystem();
        for (String war : wars) {
            if(StringUtils.isBlank(war)) {
                LOG.warn("war" + war + " is blank, override.");
                continue;
            }
            Path warPath = new Path(war);
            if(!fs.exists(warPath)) {
                LOG.warn("warPath " + warPath + " not extsis.");
                throw new IllegalArgumentException("warPath " + warPath + " not extsis.");
            }

            LOG.info("add war " + warPath.toUri().toString());
            warPathList.add(warPath);
        }

        File jettyHome = new File(pwd);
        File jettyContextDir = new File(jettyHome, "contexts");
        File jettyWebAppsDir = new File(jettyHome, "webapps");

        if(!jettyWebAppsDir.exists()) {
            LOG.info("mkdir " + jettyWebAppsDir.getAbsolutePath());
            jettyWebAppsDir.mkdirs();
        }

        if(!jettyContextDir.exists()) {
            LOG.info("mkdir " + jettyContextDir.getAbsolutePath());
            jettyContextDir.mkdirs();
        }

        new File(jettyHome, "tmp").mkdirs();

        for (Path path : warPathList) {
            if(fs.isDirectory(path)) {
                //如果是目录, 直接部署目录
                LOG.info("copy dir " + path + " to " + jettyWebAppsDir.getAbsolutePath());
                fs.copyToLocalFile(path, new Path(jettyWebAppsDir.getAbsolutePath()));
                copyXmlFile(fs, path, jettyContextDir.getAbsolutePath());
            } else {
                //如果是 war 文件, 直接部署文件
                LOG.info("copy war " + path + " to " + jettyWebAppsDir.getAbsolutePath());
                fs.copyToLocalFile(path, new Path(jettyWebAppsDir.getAbsolutePath()));
                copyXmlFile(fs, path, jettyContextDir.getAbsolutePath());
            }
        }

        String[] archives = jettyConf.getStringArray(JettyOnYarn.JETTY_YARN_ARCHIVES);
        // 添加 archives zip 文件到环境
        for (String archive : archives) {
            Path p = new Path(archive);
            LOG.info("add archive file: {}", archive);

            fs.copyToLocalFile(p, new Path(jettyHome.getAbsolutePath(), p.getName()));

            int i = p.getName().lastIndexOf(".");
            String name = (i > 0 ? p.getName().substring(0, i) : p.getName());
            ZipFileOperator.uncompressZip(new File(jettyHome, p.getName()), name);
        }

        LOG.info("start args: [" + StringUtils.join(args, " ") + "]");
        try {
            XmlConfiguration.main(args);
            LOG.info("start jetty server success.");

            LOG.info("jetty server register.");
            String publishClass = jettyConf.getString("jetty.yarn.publisher.class", LoggerJettyPublisher.class.getName());
            Class<JettyPublisher> aClass = (Class<JettyPublisher>) Class.<JettyPublisher>forName(publishClass);
            JettyPublisher jettyPublisher = aClass.newInstance();

            LOG.info("exec " + jettyPublisher.getClass().getName() + " init.");
            jettyPublisher.init(jettyConf);

            LOG.info("exec " + jettyPublisher.getClass().getName() + " publish( " + jettyHost + ":" + jettyPort + ");");
            jettyPublisher.publish(jettyHost, Integer.parseInt(jettyPort));

            LOG.info("register success...");

        } catch (Exception e) {
            LOG.error(ExceptionUtils.getFullStackTrace(e));
            System.exit(143);
        }
    }
}
